package com.aboverock.module.shiro.config;

import com.aboverock.autoconfigure.properties.LocalUserProperties;
import com.aboverock.autoconfigure.properties.ShiroProperties;
import com.aboverock.core.shiro.StatelessSubjectFactory;
import com.aboverock.core.shiro.authz.rest.RestShiroFilterFactoryBean;
import com.aboverock.core.shiro.filter.JwtShiroFilter;
import com.aboverock.module.shiro.realms.DbShiroRealm;
import com.aboverock.module.shiro.realms.JwtShiroRealm;
import com.aboverock.module.shiro.realms.LdapShiroRealm;
import com.aboverock.module.shiro.realms.LoginTokenShiroRealm;
import com.aboverock.module.shiro.realms.MyModularRealmAuthenticator;
import com.aboverock.module.shiro.service.AppKeyShiroFilter;
import com.aboverock.module.shiro.service.ShiroService;
import com.aboverock.module.system.service.AppKeyService;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import javax.servlet.Filter;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.authc.pam.FirstSuccessfulStrategy;
import org.apache.shiro.authc.pam.ModularRealmAuthenticator;
import org.apache.shiro.cache.CacheManager;
import org.apache.shiro.cache.ehcache.EhCacheManager;
import org.apache.shiro.mgt.DefaultSessionStorageEvaluator;
import org.apache.shiro.mgt.DefaultSubjectDAO;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.realm.Realm;
import org.apache.shiro.session.mgt.DefaultSessionManager;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.mgt.DefaultWebSubjectFactory;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.crazycake.shiro.RedisCacheManager;
import org.crazycake.shiro.RedisManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * The Class ShiroConfig.
 *
 * @author Rock Wang
 */
@Configuration
@EnableConfigurationProperties({ ShiroProperties.class, LocalUserProperties.class })
public class ShiroConfig {
    private Logger logger = LoggerFactory.getLogger(getClass());

    // @Autowired
    // private Flyway flyway;

    @Autowired
    private ShiroProperties shiroProperties;

    @Autowired
    private LocalUserProperties localUserProperties;

    @Autowired
    private ShiroService shiroService;

    @Autowired
    private AppKeyService appKeyService;

    /** 自定义拦截器对象名. */
    public static final String JWT_FILTER = "jwtShiroFilter";

    /** 自定义拦截器对象名. */
    public static final String APP_KEY_FILTER = "appKeyShiroFilter";

    /**
     * Shiro filter.
     *
     * @param securityManager the security manager
     * @return the shiro filter factory bean
     */
    @Bean
    public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) {
        // initDatabase();

        ShiroFilterFactoryBean shiroFilterFactoryBean = new RestShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(securityManager);

        // 自定义拦截器
        Map<String, Filter> filtersMap = new LinkedHashMap<String, Filter>();
        filtersMap.put(JWT_FILTER, new JwtShiroFilter());
        filtersMap.put(APP_KEY_FILTER, new AppKeyShiroFilter(appKeyService));
        shiroFilterFactoryBean.setFilters(filtersMap);

        Map<String, String> filterChainDefinitionMap = shiroService.loadFilterChainDefinitions();
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);

        return shiroFilterFactoryBean;
    }

    /**
     * Security manager.
     *
     * @return the default web security manager
     */
    @Bean
    public DefaultWebSecurityManager securityManager() {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        // 关闭session
        securityManager.setSubjectFactory(subjectFactory());
        securityManager.setSessionManager(sessionManager());
        ((DefaultSessionStorageEvaluator) ((DefaultSubjectDAO) securityManager.getSubjectDAO())
                .getSessionStorageEvaluator()).setSessionStorageEnabled(false);

        securityManager.setCacheManager(shiroCacheManager());
        securityManager.setAuthenticator(modularRealmAuthenticator());
        List<Realm> realms = new ArrayList<>();
        realms.add(jwtShiroRealm());
        realms.add(ldapShiroRealm());
        realms.add(dbShiroRealm());
        realms.add(loginTokenShiroRealm());
        securityManager.setRealms(realms);
        return securityManager;
    }

    /**
     * subject工厂管理器.
     *
     * @return the default web subject factory
     */
    @Bean
    public DefaultWebSubjectFactory subjectFactory() {
        StatelessSubjectFactory subjectFactory = new StatelessSubjectFactory();
        return subjectFactory;
    }

    /**
     * session管理器： sessionManager通过sessionValidationSchedulerEnabled禁用掉会话调度器.
     *
     * @return the default session manager
     */
    @Bean
    public DefaultSessionManager sessionManager() {
        DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
        sessionManager.setSessionValidationSchedulerEnabled(false);
        return sessionManager;
    }

    /**
     * Gets the cache manager.
     *
     * @return the cache manager
     */
    @Bean
    public CacheManager shiroCacheManager() {
        if (null == shiroProperties.getRedisHost()) {
            EhCacheManager ehcacheManager = new EhCacheManager();

            net.sf.ehcache.CacheManager shiroCacheManager = net.sf.ehcache.CacheManager
                    .getCacheManager("shiroCache");
            if (null == shiroCacheManager) {
                ehcacheManager.setCacheManagerConfigFile(shiroProperties.getEhcacheConfig());
            } else {
                ehcacheManager.setCacheManager(shiroCacheManager);
            }
            logger.info("Data-Smart: 使用EhCache缓存");
            return ehcacheManager;
        } else {
            RedisCacheManager redisCacheManager = new RedisCacheManager();
            redisCacheManager.setRedisManager(redisManager());
            if (StringUtils.isNotBlank(shiroProperties.getRedisPrefix())) {
                redisCacheManager.setKeyPrefix(shiroProperties.getRedisPrefix() + ":"
                        + RedisCacheManager.DEFAULT_CACHE_KEY_PREFIX);
            }
            redisCacheManager.setExpire(shiroProperties.getRedisExpire());
            logger.info("Data-Smart: 使用redis缓存");
            return redisCacheManager;
        }
    }

    @Bean
    public RedisManager redisManager() {
        RedisManager redisManager = new RedisManager();
        redisManager.setHost(shiroProperties.getRedisHost());
        redisManager.setPassword(shiroProperties.getRedisPassword());
        return redisManager;
    }

    @Bean
    public JwtShiroRealm jwtShiroRealm() {
        JwtShiroRealm jwtShiroRealm = new JwtShiroRealm();
        return jwtShiroRealm;
    }

    /**
     * Ldap shiro realm.
     *
     * @return the ldap shiro realm
     */
    @Bean
    public LdapShiroRealm ldapShiroRealm() {
        LdapShiroRealm ldapShiroRealm = new LdapShiroRealm();
        return ldapShiroRealm;
    }

    /**
     * Db shiro realm.
     *
     * @return the db shiro realm
     */
    @Bean
    public DbShiroRealm dbShiroRealm() {
        DbShiroRealm dbShiroRealm = new DbShiroRealm(shiroProperties.getCacheAuthorization(),
                localUserProperties.isSupport());
        return dbShiroRealm;
    }

    /**
     * Login token shiro realm.
     *
     * @return the login token shiro realm
     */
    @Bean
    public LoginTokenShiroRealm loginTokenShiroRealm() {
        LoginTokenShiroRealm loginTokenShiroRealm = new LoginTokenShiroRealm();
        return loginTokenShiroRealm;
    }

    /**
     * 系统自带的Realm管理，主要针对多realm.
     *
     * @return the modular realm authenticator
     */
    @Bean
    public ModularRealmAuthenticator modularRealmAuthenticator() {
        ModularRealmAuthenticator modularRealmAuthenticator = new MyModularRealmAuthenticator();
        modularRealmAuthenticator.setAuthenticationStrategy(new FirstSuccessfulStrategy());
        return modularRealmAuthenticator;
    }

    /**
     * 开启Shiro的注解(如@RequiresRoles,@RequiresPermissions),需借助SpringAOP扫描使用Shiro注解的类,并在必要时进行安全逻辑验证.
     *
     * @return the default advisor auto proxy creator
     */
    @Bean
    public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator() {
        DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
        advisorAutoProxyCreator.setProxyTargetClass(true);
        return advisorAutoProxyCreator;
    }

    /**
     * 开启shiro aop注解支持.
     * 
     * @param securityManager the security manager
     * @return the authorization attribute source advisor
     */
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(
            SecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
        return authorizationAttributeSourceAdvisor;
    }

    // private void initDatabase() {
    //     Location[] locations = flyway.getConfiguration().getLocations();
    //     Location[] newLocations = ArrayUtils.insert(0, locations,
    //             new Location("classpath:db/smart/migration"));
    //     Flyway smartFlyway = Flyway.configure().configuration(flyway.getConfiguration())
    //             .locations(newLocations).load();
    //     try {
    //         smartFlyway.migrate();
    //         logger.info("Data-Smart：数据库初始化成功");
    //     } catch (FlywayException e) {
    //         smartFlyway.repair();
    //         logger.error("Data-Smart：数据库初始化失败", e);
    //         throw e;
    //     }
    // }

}